﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DearLee.Toolkit.Model;

namespace DearLee.Toolkit {

  public class Date {

    /// <summary>
    /// 小时
    /// </summary>
    public const string HOUR = "hour";

    /// <summary>
    /// 天
    /// </summary>
    public const string DAY = "day";

    /// <summary>
    /// 周
    /// </summary>
    public const string WEEK = "week";

    /// <summary>
    /// 月
    /// </summary>
    public const string MONTH = "month";

    /// <summary>
    /// 小时所含的毫秒数
    /// </summary>
    public const int MILLISECONDS_HOUR = 3600000;

    /// <summary>
    /// 一天所含的毫秒数
    /// </summary>
    public const int MILLISECONDS_DAY = 86400000;

    /// <summary>
    /// 填补时间空洞
    /// </summary>
    /// <param name="sourceList">原集合</param>
    /// <param name="granularity">粒度</param>
    /// <returns>处理后的集合</returns>
    public static List<DataItem> FillTimeHole(List<DataItem> sourceList, DateTime startTime, DateTime endTime, string granularity) {

      List<DataItem> resultList = null;

      resultList = new List<DataItem>();
      sourceList = sourceList.OrderBy(item => item.Time).ToList();
      FillStartEndTime(sourceList, startTime, endTime, granularity);

      for (int i = 0; i < sourceList.Count; ++i) {

        if (i > 0 && !IsContinuous(sourceList[i].Time, sourceList[i - 1].Time, granularity)) {

          int intervals = 0;

          intervals = GetGranularityIntervals(sourceList[i].Time, sourceList[i - 1].Time, granularity);

          for (int j = 1; j < intervals; ++j) {

            DateTime time = DateTime.MinValue;

            switch (granularity) {
              case HOUR:
                time = sourceList[i - 1].Time.AddHours(j);
                break;
              case DAY:
                time = sourceList[i - 1].Time.AddDays(j);
                break;
              case MONTH:
                time = sourceList[i - 1].Time.AddMonths(j);
                break;
            }

            resultList.Add(new DataItem {
              Time = time,
              Quantity = 0
            });
          }

          resultList.Add(sourceList[i]);
        }
        else {
          resultList.Add(sourceList[i]);
        }
      }

      resultList = resultList.OrderBy(item => item.Time).ToList();

      return resultList;
    }

    /// <summary>
    /// 将开始时间、结束时间填充到源集合中
    /// </summary>
    /// <param name="sourceList">源集合</param>
    /// <param name="startTime">开始时间</param>
    /// <param name="endTime">结束时间</param>
    /// <param name="granularity">粒度</param>
    public static void FillStartEndTime(List<DataItem> sourceList, DateTime startTime, DateTime endTime, string granularity) {

      if (!IsSame(sourceList.First().Time, startTime, granularity)) {
        sourceList.Insert(0, new DataItem {
          Time = startTime,
          Quantity = 0
        });
      }

      if (!IsSame(sourceList.Last().Time, endTime, granularity)) {
        sourceList.Add(new DataItem {
          Time = endTime,
          Quantity = 0
        });
      }
    }

    /// <summary>
    /// 根据粒度判断两个时间是否为连续的粒度
    /// </summary>
    /// <param name="x">时间A</param>
    /// <param name="y">时间B</param>
    /// <param name="granularity">粒度</param>
    /// <returns></returns>
    public static bool IsContinuous(DateTime x, DateTime y, string granularity) {

      bool result = false;
      TimeSpan ts;

      double multiple = 0d; // 天的倍数

      switch (granularity) {
        case HOUR:
          multiple = 1d / 24d;
          break;
        case DAY:
          multiple = 1;
          break;
        case WEEK:
          multiple = 7;
          break;
        case MONTH:
          multiple = 30;
          break;
      }

      RemoveUnrelatedDateTimeProperties(ref x, granularity);
      RemoveUnrelatedDateTimeProperties(ref y, granularity);

      ts = x - y;

      if (MONTH == granularity) {

        if (
             (
               x.Year == y.Year &&
               Math.Abs(x.Month - y.Month) == 1
             )
             ||
             (
               x.Year < y.Year &&
               x.Month == 12 &&
               y.Month == 1
             )
             ||
             (
               x.Year > y.Year &&
               x.Month == 1 &&
               y.Month == 12
             )
           ) {

          return true;
        }
      }
      else if (Math.Abs(ts.TotalMilliseconds) <= MILLISECONDS_DAY * multiple) {
        result = true;
      }

      return result;
    }

    /// <summary>
    /// 判断两个时间是否在粒度上相同
    /// </summary>
    /// <param name="x">运算数一</param>
    /// <param name="y">运算数二</param>
    /// <param name="granularity">粒度</param>
    /// <returns></returns>
    public static bool IsSame(DateTime x, DateTime y, string granularity) {

      bool flag = false;

      switch (granularity) {
        case HOUR:
          if (x.Year == y.Year &&
              x.Month == y.Month &&
              x.Day == y.Day &&
              x.Hour == y.Hour) {

            flag = true;
          }
          break;
        case DAY:
          if (x.Year == y.Year &&
              x.Month == y.Month &&
              x.Day == y.Day) {

            flag = true;
          }
          break;
        case MONTH:
          if (x.Year == y.Year &&
              x.Month == y.Month) {

            flag = true;
          }
          break;
      }

      return flag;
    }

    /// <summary>
    /// 去除无关时间属性
    /// </summary>
    /// <param name="x">时间</param>
    /// <param name="granularity">粒度</param>
    public static void RemoveUnrelatedDateTimeProperties(ref DateTime x, string granularity) {

      switch (granularity) {
        case HOUR:
          x = new DateTime(x.Year, x.Month, x.Day, x.Hour, 0, 0);
          break;
        case DAY:
          x = new DateTime(x.Year, x.Month, x.Day, 0, 0, 0);
          break;
        case WEEK:
          x = x.AddHours(-x.Hour);
          break;
        case MONTH:
          x = new DateTime(x.Year, x.Month, 1, 0, 0, 0);
          break;
      }
    }

    /// <summary>
    /// 获取粒度间隔
    /// </summary>
    /// <param name="x">时间A</param>
    /// <param name="y">时间B</param>
    /// <param name="granularity">粒度</param>
    /// <returns></returns>
    public static int GetGranularityIntervals(DateTime x, DateTime y, string granularity) {

      int intervals = 0;
      double multiple = 0d;
      TimeSpan ts;

      RemoveUnrelatedDateTimeProperties(ref x, granularity);
      RemoveUnrelatedDateTimeProperties(ref y, granularity);

      ts = x - y;

      switch (granularity) {
        case HOUR:
          multiple = 1d / 24d;
          break;
        case DAY:
          multiple = 1;
          break;
        case WEEK:
          multiple = 7;
          break;
        case MONTH:
          multiple = 30;
          break;
        default:
          break;
      }

      intervals = Convert.ToInt32(Math.Abs(ts.TotalMilliseconds) / (MILLISECONDS_DAY * multiple));

      return intervals;
    }
  }
}
